package haxeframe.views;

import flash.errors.TypeError;
#if !ios
import flash.text.TextField;
import flash.text.TextFormat;
#end
import haxe.Timer;
import haxeframe.Globals;
import haxeframe.events.ViewEvent;
import haxeframe.loading.LoadManager;
import haxeframe.sound.SoundManager;
import haxeframe.utils.MathUtils;
import haxeframe.utils.Utils;

import flash.Lib;
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TouchEvent;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;

class View extends Sprite
{	
	public var removeOnHide:Bool = true;
	public var deleteOnRemove:Bool = true;

	private var _name:String;
	private var _showing:Bool;
	private var _buttons:Array<Sprite>;
	private var _initial:Map<DisplayObject, Array<Dynamic>>;
	private var _config:Xml;
	private var _strings:Xml;

	private var _touches:Map<Int, Sprite>;

	public var strings(get,set):Xml;
	public function get_strings():Xml
	{
		return _strings;
	}
	public function set_strings(value:Xml):Xml
	{
		_strings = value;
		handleStrings();
		return _strings;
	}
	
	private var _removeOnHide:Bool = true;
	private var _deleteOnRemove:Bool = true;
	
	public var transitioning:Bool;
	
	private var _typeDescription:Xml;
	
	private var _globals:Class<Dynamic> = Globals;

	public var doRender:Bool = true;

	private var resizeTimer:Timer;

	public function new()
	{
 		super ();
		if(stage == null){
			addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		} else {
			init();
		}
	}

	private function onAddedToStage(e:Event):Void
	{
		removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		init();
		config = _config;
	}

	public function View(): Void
	{
	}

	private function onResize(event:Event):Void
	{
		// if(resizeTimer != null) resizeTimer.stop();
		// resizeTimer = Timer.delay(function():Void{
			// renderLayout();
		// }, 1000);
	}

	public var config(get,set):Xml;

	public function set_config(value:Xml):Xml
	{
		_config = value;
		if(_config != null){
			handleSounds();
			handleSettings();
			if(stage != null) renderLayout();
		}
		return _config;
	}

	public function get_config():Xml
	{
		return _config;
	}

	private function init():Void
	{
		stage.addEventListener(Event.RESIZE, onResize);
		_touches = new Map<Int, Sprite>();
		_buttons = new Array<Sprite>();
		_initial = new Map<DisplayObject, Array<Dynamic>>();
	}

	private function handleSettings():Void
	{
		var c:Class<Dynamic> = Type.getClass(this);
		var instanceFields:Array<String> = Type.getInstanceFields(c);

		for(element in config.elements())
		{
			// if(Utils.valueInArray(element.nodeName, instanceFields) || Utils.valueInArray('set_'+element.nodeName, instanceFields)){
			// 	try {
			// 		Reflect.setProperty(this, element.nodeName, element.nodeValue);
			// 	} catch(e:Dynamic){

			// 	}
			// }
			if(Reflect.hasField(this, element.nodeName) || Reflect.hasField(this, 'set_'+element.nodeName)){
				try {
					if(element.exists('file')){
						Reflect.setProperty(this,element.nodeName, LoadManager.getFile(element.get('file')));
					} else {
						Reflect.setProperty(this,element.nodeName,element.firstChild());
					}
				} catch(e:TypeError) {
					// trace(e.message);
				}
			} 
		}
	}

	private function handleSounds():Void
	{
		for(sound in config.elementsNamed('sound')){
			var soundName:String = sound.get('name');
			SoundManager.addSound(soundName, LoadManager.getFile(soundName));
		}
	}

	public function reset():Void
	{
	}

	public function show():Void
	{
		showComplete();
	}

	private function showComplete():Void
	{
		this.dispatchEvent(new ViewEvent(ViewEvent.SHOW_COMPLETE));
	}

	public function hide():Void
	{
		hideComplete();
	}

	private function hideComplete():Void
	{
		this.dispatchEvent(new ViewEvent(ViewEvent.HIDE_COMPLETE));
	}

	private function initButton(button:Sprite):Void
	{
		button.mouseChildren = false;
		button.addEventListener(MouseEvent.MOUSE_OVER, handleMouseEvent);
		button.addEventListener(MouseEvent.MOUSE_OUT, handleMouseEvent);
		button.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseEvent);
		button.addEventListener(MouseEvent.MOUSE_UP, handleMouseEvent);
		button.addEventListener(MouseEvent.CLICK, handleMouseEvent);

		if(Globals.MOBILE){

			Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
			button.addEventListener(TouchEvent.TOUCH_BEGIN, mapTouchToMouse);
			button.addEventListener(TouchEvent.TOUCH_END, mapTouchToMouse);
			button.addEventListener(TouchEvent.TOUCH_OVER, mapTouchToMouse);
			button.addEventListener(TouchEvent.TOUCH_OUT, mapTouchToMouse);
			button.addEventListener(TouchEvent.TOUCH_MOVE, mapTouchToMouse);
		}
	}


	private function deactivateButton(button:Sprite):Void
	{
		if(button == null) return;

		button.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseEvent);
		button.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseEvent);
		button.removeEventListener(MouseEvent.MOUSE_DOWN, handleMouseEvent);
		button.removeEventListener(MouseEvent.MOUSE_UP, handleMouseEvent);
		button.removeEventListener(MouseEvent.CLICK, handleMouseEvent);

		if(Multitouch.supportsTouchEvents){
			button.removeEventListener(TouchEvent.TOUCH_BEGIN, mapTouchToMouse);
			button.removeEventListener(TouchEvent.TOUCH_END, mapTouchToMouse);
			button.removeEventListener(TouchEvent.TOUCH_OVER, mapTouchToMouse);
			button.removeEventListener(TouchEvent.TOUCH_OUT, mapTouchToMouse);
			button.removeEventListener(TouchEvent.TOUCH_MOVE, mapTouchToMouse);
		}
	}

	private function mapTouchToMouse(event:TouchEvent):Void
	{
		var button:Sprite;

		if(_touches.get(event.touchPointID) != null){
			button = _touches.get(event.touchPointID);
		} else {
			button = cast(event.currentTarget, Sprite);
		}

		if(button == null) return;

		switch(event.type)
		{
			case TouchEvent.TOUCH_BEGIN :
				_touches.set(event.touchPointID, button);
				button.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_DOWN, true, false, event.localX, event.localY));

			case TouchEvent.TOUCH_END :
				button.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_UP, true, false, event.localX, event.localY));

			case TouchEvent.TOUCH_OVER :
				button.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER, true, false, event.localX, event.localY));

			case TouchEvent.TOUCH_OUT :
				button.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_UP, true, false, event.localX, event.localY));

			case TouchEvent.TOUCH_MOVE :
				if(event.localX <= button.getBounds(button).left || 
					event.localY <= button.getBounds(button).top || 
					event.localX >= button.getBounds(button).right || 
					event.localY >= button.getBounds(button).bottom)
				{
					button.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_UP, true, false, event.localX, event.localY));
				}
		}
	}

	private function handleMouseEvent(event:MouseEvent):Void
	{
		if(event.type == MouseEvent.CLICK || Globals.MOBILE && event.type == MouseEvent.MOUSE_DOWN){
			var button:Sprite = cast(event.currentTarget, Sprite);	
			button.mouseEnabled = false;
			Timer.delay(function():Void
			{
				button.mouseEnabled = true;
			}, 1);
		}
	}

	private function clearLayout():Void
	{
		if(_config == null) return;
 		var assets:Iterator<Xml> = _config.elementsNamed('asset');
		for(assetXml in assets){
			var assetName:String = assetXml.get('name');
			var isChild:Bool = assetXml.get('child') == 'true';
			if(Reflect.getProperty(this, assetName) != null){
				
				if(Reflect.getProperty(this, assetName) == null && isChild)
				{
					Reflect.setProperty(this, assetName, new Sprite());
				}
				var assetReference:DisplayObject = cast(Reflect.getProperty(this, assetName), DisplayObject);

				if(isChild){
					Utils.removeChildren(cast(assetReference, Sprite));
				} else {
					if(assetReference.parent != null) assetReference.parent.removeChild(assetReference);
				}
			}
		}
		doRender = true;
	}
	
	private function renderLayout():Void
	{
		if(doRender && _config != null){

			clearLayout();

			var c:Class<Dynamic> = Type.getClass(this);
			if(c == null){
				//trace('Could not get class of '+this);
				return;
			}

			var instanceFields:Array<String> = Type.getInstanceFields(c);

			var assets:Iterator<Xml> = _config.elementsNamed('asset');
			for(assetXml in assets){
				var assetName:String = assetXml.get('name');
				var isChild:Bool = assetXml.get('child') == 'true';
				var asset:Dynamic;
				
				asset = LoadManager.getBitmap(assetXml.get('name'));

				if(asset == null) continue;

				var assetReference:Dynamic;
				
				if(isChild){
					if(Reflect.getProperty(this, assetName) == null)
					{
						Reflect.setProperty(this, assetName, new Sprite());
					}
					assetReference = Reflect.getProperty(this, assetName);
				} else if(asset != null){
					assetReference = (asset);
				} else {
					continue;
				}

				if(Utils.valueInArray(assetName, instanceFields) || Utils.valueInArray('set_'+assetName, instanceFields)){
					if(!isChild) Reflect.setProperty(this, assetName, asset);
				} else {
					//trace('No field "'+assetName+'"" in '+Type.getClassName(c));
					continue;
				}

				var ac:Class<Dynamic> = Type.getClass(assetReference);

				if(ac == null) continue;

				var assetFields:Array<String> = Type.getInstanceFields(ac);
				
				for(attribute in assetXml.attributes()){
					if((Utils.valueInArray(attribute, assetFields) || Utils.valueInArray('set_'+attribute, assetFields)) && assetReference != null){
						if(attribute == 'scaleX' || attribute == 'scaleY'){
							Reflect.setProperty(assetReference, attribute, assetXml.get(attribute));
						} else {
							Reflect.setProperty(assetReference, attribute, parseAttribute(attribute, assetXml.get(attribute), Reflect.getProperty(_globals, 'SCALE')));
						}
					} 
				}

				if(assetXml.get('width') == null)
					Reflect.setProperty(asset, 'scaleX', Reflect.getProperty(asset,'scaleX')*Reflect.getProperty(_globals, 'SCALE_FACTOR'));
				if(assetXml.get('height') == null)
					Reflect.setProperty(asset, 'scaleY', Reflect.getProperty(asset,'scaleY')*Reflect.getProperty(_globals, 'SCALE_FACTOR'));

				var aw:Float = Math.abs(Reflect.getProperty(asset,'width'));
				var ah:Float = Math.abs(Reflect.getProperty(asset,'height'));
				var ax:Float = Reflect.getProperty(asset,'x');
				var ay:Float = Reflect.getProperty(asset,'y');

				if(Reflect.getProperty(asset, 'scaleX') < 0){
					aw = -aw;
				}
				if(Reflect.getProperty(asset, 'scaleY') < 0){
					ah = -ah;
				}

				Reflect.setProperty(asset, 'x', ax-aw*.5);
				Reflect.setProperty(asset, 'y', ay-ah*.5);

				if(isChild){
					cast(assetReference, Sprite).addChild(asset);
				}
				addChild(assetReference);
			}
			doRender = false;
		}
		handleStrings();
	}

	private function handleStrings():Void
	{
	#if !ios
		if(_strings != null){
			for(element in _strings.elements())
			{
				try {
					var textField:TextField = cast(Reflect.getProperty(this, element.nodeName), TextField);
					textField.text = StringTools.replace(StringTools.replace(StringTools.replace(element.firstChild().toString(), '<![CDATA[', ''), ']]>', ''), '\t', '') + ' ';
				} catch(e:Dynamic){

				}
			}
		}
	#end
	}
	
	
	private function parseAttribute(attribute:String, val:String, scaleFactor:Float):String
	{
		if(val.indexOf('(') == 0 && val.indexOf(')') == val.length-1){
			val = Std.string(MathUtils.evaluateString(val.substr(1,val.length-2)));
		}
		
		var dir:String = attribute;
		
		if(attribute == 'x' || Std.string(attribute).toLowerCase().indexOf('width') >= 0){
			dir = 'x';
		}
		
		if(attribute == 'y' || Std.string(attribute).toLowerCase().indexOf('height') >= 0){
			dir = 'y';
		}
		
		var isNegative:Bool = false;
		if(val.indexOf('-') == val.length-1){
			isNegative = true;
			val = val.substr(0,val.length-1);
		}
		if(val.indexOf('%') == val.length-1){
			val = val.substr(0,-1);
			switch(dir){
				case 'x' :
					val = Std.string(Globals.CONTENT_WIDTH/scaleFactor*Std.parseFloat(val)/100);
				
				case 'y' :
					val = Std.string(Globals.CONTENT_HEIGHT/scaleFactor*Std.parseFloat(val)/100);
			}
		}
		if(Std.parseFloat(val) > 0 && val.indexOf('0x') != 0){
			
			if(stage != null){
				switch(dir){
					case 'x' :
						if(isNegative) val = Std.string(Globals.CONTENT_WIDTH/scaleFactor - Std.parseFloat(val));
					
					case 'y' :
						if(isNegative) val = Std.string(Globals.CONTENT_HEIGHT/scaleFactor - Std.parseFloat(val));
				}
			} else {
				// trace('Unable to calculate inverse position. stage == null');
			}
			val = Std.string(Std.parseFloat(val)*scaleFactor);
		}
		return val;
	}

}